昨天我們談了蠻多概念的,今天就讓我們具體談 Coroutine 的實作細節吧!
Suspend 是使用 Coroutine 的一個重要的保留關鍵字,用來作為讓 compiler 以及開發者識別目前是否在 Coroutine 中的一個判斷,任何的 function 都可以在他的前面加上 suspend 來讓他變成 suspend function,而其他地方如果要呼叫這個 function 就必須在某個 suspend function 或是 coroutineScope 中,比如以下的範例:
fun normalFunction() {
test() // this will not compile!!
}
suspend fun suspendFunction() {
test() // this will compile!!
}
suspend fun test() {
//...
}
如果你熟悉 Comose 的話,可能會覺得概念以及結構很類似對吧!
第一個 function 會帶出以下的錯誤:
Suspend function 'coroutineFunction' should be called only from a coroutine or another suspend function
好,那為什麼要這樣設計呢,主要是因為 suspend function 就如它的名字所示,執行的時候是可能會中斷跳出讓別人先執行後再回來繼續執行的,比如以下的程式碼就會先印出 Hello world!
後等一秒再繼續印出 Hello world 2!
:
suspend fun test() {
println("Hello World!")
delay(1000L)
println("Hello World 2!")
}
目前 Coroutine 的做法是在 compile 時多加一個參數紀錄執行到哪一個區間以及當時的環境變數值,然後把 function 內的程式碼分成多段以便隨時中斷。所以當 suspend function 呼叫 suspend function 時就可以把這個參數繼續往下傳,但如果是一般的 function,因為他沒有這個參數,compiler 自然也就不知道怎麼 compile 囉。
作為上層的開發者,其實我們不需要太擔心 suspend 的實作細節,而且也不應該依賴於底層的實作細節以免哪天改了上層就壞掉,但如果有大概的 mindset,也會讓我們更容易的去理解這個工具唷!
Suspend function 還有個重要的概念是 non-blocking,當我們使用 Thread.sleep
的時候,sleep
會真的把目前的 Thread 停下來,但使用 delay 就不會,只是把目前的 function 先回傳,等下次該執行的時候再呼叫同個 function 就好,也因此使用起來的效率會更高。
既然 suspend function 只能被其他的 suspend function 呼叫,那在雞生蛋、蛋生雞的循環裡,誰是第一個雞、或是第一個蛋呢?
大家可以翻翻看昨天的範例裡,我們通常是使用 runBlocking 這個 function 的 lambda block 來呼叫 suspend function,這好處是非常簡單就可以介接 Coroutine 跟一般的 function,但缺點就是 runBlocking 需要 block 目前的 Thread,等到 Coroutine 都執行完才能繼續走到 runBlocking 的下一行程式碼。
另一種方法是使用 CoroutineScope 的 launch,顧名思義 launch 呼叫後跟原本的 function block 就不太有關係了,除了等待執行完成,也可以透過 launch 回傳的 job 物件呼叫 cancel 來強制中斷,或是呼叫 CoroutineScope 的 cancel。
fun main() = runBlocking {
val job = launch {
...
}
job.cancel()
println("Hello")
}
大家如果翻一下程式碼,會發現 CoroutineScope 的 cancel 內部也是使用 job 的 cancel 喔 XD
Coroutine 真的有太多事情可以分享了,明天我們再繼續介紹怎麼使用 Coroutine 切換 Thread,把非同步的程式寫的跟一般程式一樣,以及 Android / iOS 上的使用。